package org.korosoft.rusalad.syncnrun.cli;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.korosoft.rusalad.syncnrun.Client;
import org.korosoft.rusalad.syncnrun.Main;
import org.korosoft.rusalad.syncnrun.tools.InsecureSocketFactory;
import org.korosoft.rusalad.syncnrun.tools.KeyStore;
import org.korosoft.rusalad.syncnrun.tools.SecureSocketFactory;
import org.korosoft.rusalad.syncnrun.tools.SocketFactory;

import java.io.File;
import java.security.PublicKey;
import java.util.Collection;

/**
 * CLI client-related command. Includes support for such arguments as host and port.
 *
 * @author Dmitry Korotkov
 * @since 1.0
 */
public abstract class CliClientCommand extends CliCommand {
    private static final Log log = LogFactory.getLog(CliClientCommand.class);

    protected String host = Main.DEFAULT_HOST;
    protected int port = Main.DEFAULT_PORT;

    protected CliClientCommand(String name, String description) {
        super(name, description);
        options.addOption("h", "host", true, String.format("Remote syncrun host (default is %s)", Main.DEFAULT_HOST));
        options.addOption("p", "port", true, String.format("Specify syncrun server port (default is %d)", Main.DEFAULT_PORT));
        options.addOption(null, "insecure", false, "Connect to insecure server (without authentication/encryption)");
        options.addOption("k", "server-key", true, "Server public key");
        options.addOption(null, "keystore", true, "Path to key store. Default is ~/.syncnrun");
        options.addOption(null, "retries", true, String.format("Number of retries for command send (default is %d)", Main.DEFAULT_RETRIES));
        preProcessors.add(new PreProcessor() {
            @Override
            public void preProcess(CommandLine commandLine) {
                if (commandLine.hasOption("h")) {
                    host = commandLine.getOptionValue("h");
                }
                if (commandLine.hasOption("p")) {
                    port = Integer.parseInt(commandLine.getOptionValue("p"));
                }
            }
        });
    }

    protected final void doExecute(CommandLine commandLine) throws Exception {
        final SocketFactory socketFactory;
        if (commandLine.hasOption("insecure")) {
            socketFactory = new InsecureSocketFactory();
        } else {
            final KeyStore keyStore;
            if (commandLine.hasOption("keystore")) {
                keyStore = new KeyStore(new File(commandLine.getOptionValue("keystore")));
            } else {
                keyStore = new KeyStore(new File(System.getProperty("user.home"), ".syncnrun"));
            }
            final Collection<PublicKey> publicKeys;
            if (commandLine.hasOption("server-key")) {
                String serverKey = commandLine.getOptionValue("server-key");
                publicKeys = KeyStore.loadPublicKeys(new File(serverKey));
            } else {
                publicKeys = keyStore.getPublicKeys();
            }
            if (publicKeys.size() != 1) {
                log.error("Found " + publicKeys.size() + " server keys. Please specify server key file containing exactly one server public key.");
                log.debug(String.format("Exiting with exit code %d", 1));
                System.exit(1);
            }
            socketFactory = new SecureSocketFactory(keyStore.getPrivateKey(), publicKeys);
        }
        final Client client;
        if (commandLine.hasOption("retries")) {
            client = new Client(socketFactory, host, port, Integer.parseInt(commandLine.getOptionValue("retries")));
        } else {
            client = new Client(socketFactory, host, port);
        }
        int retCode;
        try {
            retCode = doExecute(client, commandLine);
        } finally {
            client.stop();
        }
        log.debug(String.format("Exiting with exit code %d", retCode));
        System.exit(retCode);
    }

    protected abstract int doExecute(Client client, CommandLine commandLine) throws Exception;

}
